﻿using System;
using System.Collections;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Basmus.Conel;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;

namespace Basmus.Tests
{
    [TestClass]
    public class CollectionTypesBindingBlackBoxTest
    {
        private IBinder _binder;

        private Binding _binding;

        [TestInitialize]
        public void Initialize()
        {
            _binder = new StandardKernel(new TestNinjectModule()).Get<IBinder>();
        }

        #region simple tests

        [TestMethod]
        public void GettingIntElementsForArrayContainerTest()
        {
            //Arrange
            var containerType = typeof(int[]);
            var containerObjs = new int[][]
                                    {
                                        new int[] { 0, 1, 4, 6, -1, -100000323 },
                                        new int[] { 9879, 22, 22 },
                                        new int[] { 7 },
                                        new int[0],
                                    };

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int));

            //Act & Assert
            foreach (var containerObj in containerObjs)
            {
                var elements = _binding.GetElements(containerObj);
                Assert.IsTrue(elements.Length == containerObj.Length);

                for (int i = 0; i < elements.Length; ++i)
                {
                    Assert.IsTrue(elements[i] is int);
                    Assert.IsTrue((int)elements[i] == containerObj[i]);
                }
            }
        }

        [TestMethod]
        public void GettingIntElementsForNullArrayContainerTest()
        {
            //Arrange
            var containerType = typeof(int[]);

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int));

            //Act & Assert
            var elements = _binding.GetElements(null);
            Assert.IsNull(elements);
        }

        [TestMethod]
        public void GettingIntArrayContainerTest()
        {
            //Arrange
            var containerType = typeof(int[]);
            var elementSets = new[]
                                 {
                                     new object[] {0}, 
                                     new object[] {1}, 
                                     new object[] {6, 554654, -1231331, 4234, 4}, 
                                     new object[] {},
                                     new object[] {-1, 5, 7},
                                     new object[] {-100000323}
                                 };
            var expectedContainers = new int[][]
                           {
                               new int[] {0}, 
                               new int[] {1}, 
                               new int[] {6, 554654, -1231331, 4234, 4}, 
                               new int[] {},
                               new int[] {-1, 5, 7},
                               new int[] {-100000323}
                           };

            //Act & Assert
            _binding = _binder.Bind(containerType);
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(int));

            //Act & Assert
            for (int i = 0; i < expectedContainers.Length; ++i)
            {
                var containerObj = _binding.GetContainer(elementSets[i]);

                Assert.IsInstanceOfType(containerObj, typeof(int[]));
                var container = (int[])containerObj;
                Assert.AreEqual(container.Length, expectedContainers[i].Length);

                for (int j = 0; j < container.Length; ++j)
                {
                    Assert.AreEqual(container[j], expectedContainers[i][j]);
                }
            }
        }

        #endregion

        public void GettingElementsForContainerTestHelper<TElement, TCollection>() where TCollection : class, IEnumerable<TElement>
        {
            //Arrange
            var containers = ElementsHelper.GetElementsOf<TElement>()
                                           .Where(set => set != null)
                                           .Select(ContainerHelper.GenerateCollectionContainers<TCollection, TElement>);

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            foreach (var container in containers)
            {
                var elements = _binding.GetElements(container);
                Assert.IsTrue(elements.Length == container.Count());

                for (int i = 0; i < elements.Length; ++i)
                {
                    if (!typeof(TElement).IsValueType && elements[i] == null)
                    {
                        Assert.IsNull(elements[i]);
                        object obj = container.ElementAt(i);
                        Assert.IsNull(obj);
                    }
                    else
                    {
                        Assert.IsInstanceOfType(elements[i], typeof(TElement));
                        Assert.AreEqual((TElement)elements[i], container.ElementAt(i));
                    }
                }
            }
        }

        public void GettingElementsForNullContainerTestHelper<TElement, TCollection>() where TCollection : class, IEnumerable<TElement>
        {
            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            var elements = _binding.GetElements(null);
            Assert.IsNull(elements);
        }

        [TestMethod]
        public void IncompatibleTypesTest()
        {
            //Arrange
            var elementTypes = TypeHelper.EveryPossibleElementTypes();

            var arrayPairs = elementTypes.Select(t => new { ElementType = t, CollectionType = t.MakeArrayType() });
            var collectionPairs = elementTypes.SelectMany(t => TypeHelper.CollectionGenericTypeDefinitions()
                                                                         .Select(c => new { ElementType = t, CollectionType = c.MakeGenericType(t) }));

            var pairs = arrayPairs.Concat(collectionPairs);

            //Act & Assert
            foreach (var pair in pairs)
            {
                foreach (var incompatibleType in elementTypes.Except(new[] { pair.ElementType }))
                {
                    _binding = _binder.Bind(pair.CollectionType, incompatibleType);
                    Assert.IsNull(_binding);
                }
            }
        }

        //[TestMethod]
        //public void GettingElementsTest()
        //{
        //    //Arrange
        //    var elementTypes = TypeHelper.EveryPossibleElementTypes();

        //    var arrayPairs = elementTypes.Select(t => new {ElementType = t, CollectionType = t.MakeArrayType()});
        //    var collectionPairs = elementTypes.SelectMany(t => TypeHelper.CollectionGenericTypeDefinitions()
        //                                                                 .Select(c => new {ElementType = t, CollectionType = c.MakeGenericType(t)}));

        //    var pairs = arrayPairs.Concat(collectionPairs);

        //    //Act & Assert
        //    foreach (var pair in pairs)
        //    {
        //        _binding = _binderService.Bind(pair.CollectionType);
        //        this.Invoke("GettingElementsForContainerTestHelper", pair.ElementType, pair.CollectionType);

        //        _binding = _binderService.Bind(pair.CollectionType, pair.ElementType);
        //        this.Invoke("GettingElementsForContainerTestHelper", pair.ElementType, pair.CollectionType);

        //        _binding = _binderService.Bind(pair.CollectionType);
        //        this.Invoke("GettingElementsForNullContainerTestHelper", pair.ElementType, pair.CollectionType);

        //        _binding = _binderService.Bind(pair.CollectionType, pair.ElementType);
        //        this.Invoke("GettingElementsForNullContainerTestHelper", pair.ElementType, pair.CollectionType);
        //    }
            
        //}

        public void GettingValueTypeContainerTestHelper<TElement, TCollection>() where TCollection : class, IEnumerable<TElement>
        {
            //Arrange
            var expectedContainers = ElementsHelper.GetElementsOf<TElement>()
                                            .Where(set => set != null)
                                            .Select(ContainerHelper.GenerateCollectionContainers<TCollection, TElement>)
                                            .ToArray();

            var elementSets = expectedContainers.Select(set => set.Cast<object>().ToArray()).ToArray();

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof (TElement));

            //Act & Assert
            for (int i = 0; i < expectedContainers.Length; ++i)
            {
                var containerObj = _binding.GetContainer(elementSets[i]);

                Assert.IsInstanceOfType(containerObj, typeof(TCollection));
                var container = (TCollection)containerObj;
                var expectedContainer = expectedContainers[i];
                Assert.AreEqual(container.Count(), expectedContainer.Count());
                CollectionAssertHelper.AreEqual(container, expectedContainer);
            }
        }

        public void GettingValueTypeContainerForNullElementSetTestHelper<TElement, TCollection>() where TCollection : class, IEnumerable<TElement>
        {
            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(TElement));

            //Act & Assert
            var container = _binding.GetContainer(null);
            Assert.IsNull(container);
        }

        [TestMethod]
        public void GettingValueTypeContainerTest()
        {
            //Arrange
            var elementTypes = TypeHelper.ValueTypes();

            var arrayPairs = elementTypes.Select(t => new { ElementType = t, CollectionType = t.MakeArrayType() });
            var collectionPairs = elementTypes.SelectMany(t => TypeHelper.CollectionGenericTypeDefinitions()
                                                                         .Select(c => new { ElementType = t, CollectionType = c.MakeGenericType(t) }));

            var pairs = arrayPairs.Concat(collectionPairs);

            //Act & Assert
            foreach (var pair in pairs)
            {
                _binding = _binder.Bind(pair.CollectionType);
                this.Invoke("GettingValueTypeContainerTestHelper", pair.ElementType, pair.CollectionType);

                _binding = _binder.Bind(pair.CollectionType, pair.ElementType);
                this.Invoke("GettingValueTypeContainerTestHelper", pair.ElementType, pair.CollectionType);

                _binding = _binder.Bind(pair.CollectionType);
                this.Invoke("GettingValueTypeContainerForNullElementSetTestHelper", pair.ElementType, pair.CollectionType);

                _binding = _binder.Bind(pair.CollectionType, pair.ElementType);
                this.Invoke("GettingValueTypeContainerForNullElementSetTestHelper", pair.ElementType, pair.CollectionType);
            }

        }

        public void GettingStringContainerTestHelper<TCollection>() where TCollection : class, IEnumerable<string>
        {
            //Arrange
            var sets = ElementsHelper.GetElementsOf<string>().Where(set => set != null).ToArray();

            var expectedContainers = sets.Select(set => set.Select(x => x ?? ""))
                                         .Select(ContainerHelper.GenerateCollectionContainers<TCollection, string>)
                                         .ToArray();

            var elementSets = sets.Select(set => set.Cast<object>().ToArray()).ToArray();

            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(string));

            //Act & Assert
            for (int i = 0; i < expectedContainers.Length; ++i)
            {
                var containerObj = _binding.GetContainer(elementSets[i]);

                Assert.IsInstanceOfType(containerObj, typeof(TCollection));
                var container = (TCollection)containerObj;
                var expectedContainer = expectedContainers[i];
                Assert.AreEqual(container.Count(), expectedContainer.Count());
                CollectionAssertHelper.AreEqual(container, expectedContainer);
            }
        }

        public void GettingStringContainerForNullElementSetTestHelper<TCollection>() where TCollection : class, IEnumerable<string>
        {
            //Act & Assert
            Assert.IsNotNull(_binding);
            Assert.AreEqual(_binding.ElementType, typeof(string));

            //Act & Assert
            var container = _binding.GetContainer(null);
            Assert.IsNull(container);
        }

        [TestMethod]
        public void GettingStringContainerTest()
        {
            //Arrange
            var collectionTypes = TypeHelper.CollectionGenericTypeDefinitions()
                                            .Select(c => c.MakeGenericType(typeof(string)))
                                            .Concat(new[] { typeof(string).MakeArrayType() });

            //Act & Assert
            foreach (var collectionType in collectionTypes)
            {
                _binding = _binder.Bind(collectionType);
                this.Invoke("GettingStringContainerTestHelper", collectionType);

                _binding = _binder.Bind(collectionType, typeof(string));
                this.Invoke("GettingStringContainerTestHelper", collectionType);

                _binding = _binder.Bind(collectionType);
                this.Invoke("GettingStringContainerForNullElementSetTestHelper", collectionType);

                _binding = _binder.Bind(collectionType, typeof(string));
                this.Invoke("GettingStringContainerForNullElementSetTestHelper", collectionType);
            }
            
        }
    }
}
